package net.martinimix.beans.support;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * Provides a prototype bean factory to create prototype beans of the appropriate
 * type based on prototype beans from the application context.
 *  
 * @author Scott Rossillo
 *
 */
public class PrototypeBeanFactory implements ApplicationContextAware {
	
	private ApplicationContext context;
	private final Map prototypes = new HashMap();
	
	/**
	 * Creates a new prototype bean factory.
	 */
	protected PrototypeBeanFactory() { }
	
	/**
	 * Adds a prototype definition for which this bean factory can
	 * create new instances.
	 * 
	 * @param name the name of the prototype bean in the application context
	 * 
	 * @param requiredClass the required <code>Class</code> to which the
	 * bean declared in the application context must be assignable to
	 */
	protected void addPrototype(String name, Class requiredClass) {
		prototypes.put(name, requiredClass);
	}
	
	/**
	 * Creates a new instance of the request bean. 
	 * 
	 * @param bean the name of the bean to be returned
	 * 
	 * @return a new instance of the given <code>bean</code>
	 * 
	 * @throws IllegalArgumentException if the named <code>bean</code> is not a known
	 * prototype to this factory
	 */
	protected final Object createBean(String bean) {
		
		if(!prototypes.keySet().contains(bean)) {
			throw new IllegalArgumentException("No prototype bean named [" + bean + "] is managed by this factory!");
		}
		return context.getBean(bean);
	}

	/* (non-Javadoc)
	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
	 */
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		
		String bean;
		Class beanClass;
		
		this.context = applicationContext;
		
		// do some checks to make sure we have what we need
		for(Iterator it = prototypes.keySet().iterator(); it.hasNext(); ) {
			bean = (String) it.next();
			beanClass = (Class) prototypes.get(bean);
			
			if(!context.isPrototype(bean)) {
				throw new FatalBeanException("Application context must contain a prototype [" + bean + "] bean!");
			}
			if(!beanClass.isAssignableFrom(context.getType(bean))) {
				throw new BeanNotOfRequiredTypeException(
						bean, beanClass, applicationContext.getType(bean));
			}
		}
	}
	
}
